/*
 *
 *  Connection Manager
 *
 *  Copyright (c) 2010 The Chromium OS Authors.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>

#include "connman.h"

#define CONNMAN_API_SUBJECT_TO_CHANGE
#include <connman/plugin.h>
#include <connman/notifier.h>
#include <connman/resolver.h>
#include <connman/log.h>

#include <glib.h>


#define	_DBG_RESOLVFILES(fmt, arg...)	DBG(DBG_RESOLVFILES, fmt, ## arg)

/*
 * The resolvfiles resolver back end creates one file per interface
 * suitable for being used as a resolv.conf file, based on the
 * supplied domain, search list, and servers. As the default interface
 * changes, STATEDIR/resolv.conf is updated to be a link to the
 * current default. The link is removed when the default interface is
 * NULL.
 */

/* Base name of the resolver file in the state directory */
#define RESOLVFILE_NAME "resolv.conf"

/* Full path to write the resolver file */
#define RESOLVFILE     STATEDIR "/" RESOLVFILE_NAME

#define RESOLVFILE_NEW RESOLVFILE ".new"

static int resolvfiles_set(const struct connman_resolver_state *entry)
{
	char *filename;
	int ret;

	_DBG_RESOLVFILES("append interface %s domain %s search_domains %p"
	    " servers %p", entry->interface, entry->domain,
	    entry->search_domains, entry->servers);

	filename = g_strdup_printf("%s.%s", RESOLVFILE, entry->interface);
	ret = connman_resolvfile_write(filename, entry->domain,
	    entry->search_domains, entry->servers);
	g_free(filename);

	return ret;
}


static int resolvfiles_unset(const struct connman_resolver_state *entry)
{
	char *filename;
	struct stat sb;

	_DBG_RESOLVFILES("remove interface %s", entry->interface);

	filename = g_strdup_printf("%s.%s", RESOLVFILE, entry->interface);
	if (stat(filename, &sb) >= 0) {
		/*
		 * If the per-interface resolv.conf file is still
		 * installed as the default just truncate it so
		 * subsequent writes update the active resolv.conf.
		 * Otherwise unlink the file.  This handles the
		 * case where ipconfig records are changed via the
		 * dbus as blindly unlink'ing the file will leave
		 * stale data in resolv.conf.
		 *
		 * NB: This is inherently racey; we assume we are
		 *     the only process mucking with these files.
		 */
		if (sb.st_nlink > 1) {
			if (truncate(filename, 0) < 0)
				connman_error("%s: truncate: %s",
				    entry->interface, strerror(errno));
		} else
			(void) unlink(filename);
	}
	g_free(filename);

	return 0;
}

static struct connman_resolver resolvfiles_resolver = {
	.name		= "resolvfiles",
	.priority	= CONNMAN_RESOLVER_PRIORITY_DEFAULT,
	.set		= resolvfiles_set,
	.unset		= resolvfiles_unset,
};


static void resolvfiles_default_changed(struct connman_service *service)
{
	const char *interface;
	char *filename;
	int err;

	if (service == NULL) {
		/*
		 * There is no default; this can happen when no
		 * devices are operational (e.g. wired unplugged
		 * and wireless down).  Be paranoid here: there
		 * should be no servers as everything should be
		 * torn down; this is just in case.
		 */
		unlink(RESOLVFILE);
		return;
	}

	interface = connman_service_get_interface(service);
	if (interface == NULL) {
		connman_error("%s: no interface for service %p", __func__,
		    service);
		return;
	}

	_DBG_RESOLVFILES("Default changed to interface %s", interface);

	filename = g_strdup_printf("%s.%s", RESOLVFILE, interface);

	/* Set up resolv.conf link, atomically replacing existing one. */
	err = link(filename, RESOLVFILE_NEW);
	if (err != 0) {
		connman_error("%s: Couldn't create %s link to %s (%s)",
			      __func__, RESOLVFILE_NEW, filename,
			      strerror(errno));
		g_free(filename);
		return;
	}
	g_free(filename);

	err = rename(RESOLVFILE_NEW, RESOLVFILE);
	if (err != 0)
		connman_error("%s: Couldn't rename %s to %s",
			      __func__, RESOLVFILE_NEW, RESOLVFILE);
	/* Clean up either in case of error or in case resolv.conf was
	 * already a link to resolv.conf.$INTERFACE, in which case
	 * rename() is a no-op.
	 */
	unlink(RESOLVFILE_NEW);
}

static struct connman_notifier resolvfiles_notifier = {
	.name		= "resolvfiles",
	.priority	= CONNMAN_NOTIFIER_PRIORITY_HIGH,
	.default_changed= resolvfiles_default_changed,
};


static int resolvfiles_init(void)
{
	int err;

	err = connman_resolver_register(&resolvfiles_resolver);
	if (err < 0)
		return err;
	return connman_notifier_register(&resolvfiles_notifier);
}

static void resolvfiles_exit(void)
{
	connman_resolver_unregister(&resolvfiles_resolver);
	connman_notifier_unregister(&resolvfiles_notifier);
	unlink(RESOLVFILE);
}


CONNMAN_PLUGIN_DEFINE(resolvfiles, "Multiple-interface resolv.conf manager",
		      VERSION, CONNMAN_PLUGIN_PRIORITY_DEFAULT,
		      resolvfiles_init, resolvfiles_exit)
